package biz.datalk.industrialland.common.shiro;


import biz.datalk.commons.utils.ip.IPUtil;
import biz.datalk.commons.utils.json.JsonUtil;
import biz.datalk.industrialland.common.exception.DatalkTokenException;
import biz.datalk.industrialland.common.result.UnifyResult;
import biz.datalk.industrialland.common.result.UnifyResultCode;
import biz.datalk.industrialland.common.util.JwtUtil;
import com.google.common.net.MediaType;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * @author tarofang@163.com
 * @date 2019-12-18
 */
@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {

    private void lazyInit() {
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        try {
            this.executeLogin(request, response);
            return true;
        } catch (DatalkTokenException dte) {
            request.setAttribute("custom_err_code", dte.getCode());
            request.setAttribute("custom_err_msg", dte.getMessage());
            throw dte;
        } catch (Exception ex) {
            log.error("Login fail: {}", ex.getMessage(), ex);
            return false;
        }
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        String token = JwtUtil.getRequestToken(httpServletRequest);
        JwtToken jwtToken = new JwtToken(token);

        this.getSubject(request, response).login(jwtToken);
        return true;
    }

    /**
     * 表示当访问拒绝时是否已经处理了；如果返回true表示需要继续处理；
     * 如果返回false表示该拦截器实例已经处理了，将直接返回即可。
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        printLog(request);
        String errCode = UnifyResultCode.TOKEN_TIMEOUT;
        String errMsg = "您没有登录或登录超时";
        try {
            Object errMsgObj = request.getAttribute("custom_err_msg");
            if (errMsgObj instanceof String) {
                errMsg = (String) errMsgObj;
            }
            Object errCodeObj = request.getAttribute("custom_err_code");
            if (errCodeObj instanceof String) {
                errCode = (String) errCodeObj;
            }
        } catch (Exception ex) {
            log.warn("{}", ex.getMessage());
        }

        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setContentType(MediaType.JSON_UTF_8.toString());

        try (PrintWriter out = response.getWriter()) {
            UnifyResult result = UnifyResult.fail(errCode, errMsg);
            String jsonStr = JsonUtil.getJsonFromObject(result);
            if (log.isDebugEnabled()) {
                log.debug("onAccessDenied ===> {}", jsonStr);
            }
            out.write(jsonStr);
            out.flush();
        }

        return Boolean.FALSE;
    }

    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        lazyInit();
        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        if (log.isDebugEnabled()) {
            log.debug("request uri: {}", httpServletRequest.getRequestURL());
        }

        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        // 跨域时会首先发送一个option请求，这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }

        return super.preHandle(request, response);
    }

    private void printLog(ServletRequest servletRequest) {
        log.warn("DatalkJwtFilter.onAccessDenied:printLog ======= into");
        try {
            HttpServletRequest request = null;
            if (servletRequest instanceof HttpServletRequest) {
                request = (HttpServletRequest) servletRequest;
            }
            if (request == null) {
                return;
            }

            String realIp = IPUtil.getIpAddress(request);
            String requestURI = request.getRequestURI();
            Enumeration<String> parameterNames = request.getParameterNames();
            Map<String, String> urlParams = new HashMap<>();
            String paramName;
            while (parameterNames.hasMoreElements()) {
                paramName = parameterNames.nextElement();
                urlParams.put(paramName, request.getParameter(paramName));
            }
            String bodyParams = "";
            try {
                bodyParams = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
            } catch (Exception ex) {
                log.error("{}", ex.getMessage(), ex);
            }

            log.warn("DatalkJwtFilter.onAccessDenied:printLog ===> uri={}. realIp={}, urlParams={}, bodyParams={}", requestURI, realIp, JsonUtil.getJsonFromObject(urlParams, Boolean.TRUE), bodyParams);
        } catch (Exception ex) {
            log.error("{}", ex.getMessage(), ex);
        } finally {
            log.warn("DatalkJwtFilter.onAccessDenied:printLog ======= out");
        }
    }

}
